package net.wangds.dymodel.ddl;

import net.wangds.common.base.module.Holder;
import net.wangds.dymodel.meta.entity.DyModelDefEntity;
import net.wangds.dymodel.meta.entity.DyModelPropDefEntity;
import net.wangds.dymodel.meta.repo.db.DyModelDefRepo;
import net.wangds.dymodel.meta.repo.db.DyModelPropDefRepo;
import net.wangds.dymodel.utls.DynamicModelUtils;
import net.wangds.jdbc.comp.jdbc.JdbcComp;
import net.wangds.log.helper.LogHelper;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static net.wangds.dymodel.utls.DynamicModelUtils.toFullTableName;

/**
 * .
 * <p></p>
 *
 * @author 王东石 2021/2/16 0:59.
 */
@Service
public class DyModelDdlComp {

    private final String DEFAULT_ENGINE = "InnoDB";
    private final String DEFAULT_CHARSET = "utf8mb4";

    @Autowired
    private JdbcComp mmCmpJdbc;


    @Autowired
    private DyModelDefRepo mmRepoMdl;

    @Autowired
    private DyModelPropDefRepo mmRepoProp;


    public void dropTable(DyModelDefEntity model) {
        String sql = "drop table if exists " + toFullTableName(model);
        mmCmpJdbc.executeUpdate(sql);
    }


    public void createTable(DyModelDefEntity model) {

        String dyModelId = model.getId();

        List<DyModelPropDefEntity> columnDefs = mmRepoProp.findByDyModelIdOrderBySeq(dyModelId);

        StringBuffer sb = new StringBuffer();
        String tn = toFullTableName(model);
        sb.append("CREATE TABLE IF NOT EXISTS ")
                .append(tn)
                .append(" ( `id` varchar(36) not null COMMENT '主键', " +
                        "`removed` int(11) DEFAULT NULL COMMENT '是否删除', " +
                        "`ctm` bigint(20) DEFAULT NULL COMMENT '创建时间', " +
                        "`disable` int(11) DEFAULT NULL COMMENT '禁用',");

        if (columnDefs != null) {
            columnDefs.forEach(def -> sb.append(toColumnDef(def)).append(","));
        }

        sb.append(" PRIMARY KEY (`id`) ) ENGINE=")
                .append(DEFAULT_ENGINE)
                .append(" DEFAULT CHARSET=")
                .append(DEFAULT_CHARSET);
        String sql = sb.toString();
        mmCmpJdbc.executeUpdate(sql);

        String remark = model.getRemark();
        if (StringUtils.isNotEmpty(remark)) {
            String sqlComment = "ALTER TABLE " + tn +
                    " COMMENT = '" + StringEscapeUtils.escapeSql(remark) + "'";
            mmCmpJdbc.executeUpdate(sqlComment);
        }

        //dropFullTextIndex(model);
        //createFullTextIndex(model);

    }

    public void dropFullTextIndex(DyModelDefEntity model) {
        StringBuilder sb = new StringBuilder();
        sb.append("ALTER TABLE ")
                .append(toFullTableName(model))
                .append(" DROP INDEX `")
                .append(DynamicModelUtils.buildFullTextIndexName(model));

        try {
            mmCmpJdbc.executeUpdate(sb.toString());
        } catch (Exception e) {
            LogHelper.warn(e);
        }
    }

    public void createFullTextIndex(DyModelDefEntity model) {
        String dyModelId = model.getId();

        List<DyModelPropDefEntity> columnDefs = mmRepoProp.findByDyModelIdOrderBySeq(dyModelId);

        List<DyModelPropDefEntity> lsFullTextSearch = columnDefs.stream().filter(c -> c.getFullTextSearch() > 0).collect(Collectors.toList());

        if (CollectionUtils.isEmpty(lsFullTextSearch)) {
            return;
        }

        StringBuffer sb = new StringBuffer();
        //ALTER TABLE `test`.`test`
        //ADD FULLTEXT INDEX  `FULL_IDX` (`testcol`) ;
        sb.append("ALTER TABLE ")
                .append(toFullTableName(model))
                .append(" ADD FULLTEXT INDEX `")
                .append(DynamicModelUtils.buildFullTextIndexName(model))
                .append("` (");

        lsFullTextSearch.forEach(c -> {
            sb.append(" ").append(DynamicModelUtils.toColumnName(c)).append(",");
        });

        if(lsFullTextSearch.size()==1){
            sb.append(" disable,");
        }

        if (sb.charAt(sb.length() - 1) == ',') {
            sb.deleteCharAt(sb.length() - 1);
        }

        sb.append(")");

        try {
            mmCmpJdbc.executeUpdate(sb.toString());
        } catch (Exception e) {
            LogHelper.warn(e);
        }
    }


    public void alterColumn(DyModelPropDefEntity column) {
        if (column == null) {
            return;
        }
        mmRepoMdl.findById(column.getDyModelId()).ifPresent(mdl -> {
            String sql = "ALTER TABLE " + toFullTableName(mdl)
                    + " CHANGE COLUMN " + DynamicModelUtils.toColumnName(column)
                    + " " + toColumnDef(column);
            mmCmpJdbc.executeUpdate(sql);
        });
    }

    public void addColumn(DyModelPropDefEntity column) {
        if (column == null) {
            return;
        }
        mmRepoMdl.findById(column.getDyModelId()).ifPresent(mdl -> {
            String sql = "ALTER TABLE " + toFullTableName(mdl)
                    + " ADD COLUMN "
                    + toColumnDef(column);
            mmCmpJdbc.executeUpdate(sql);
        });
    }


    public void dropColumn(DyModelPropDefEntity column) {
        if (column == null) {
            return;
        }
        mmRepoMdl.findById(column.getDyModelId()).ifPresent(mdl -> {
            String sql = "ALTER TABLE " + toFullTableName(mdl)
                    + " DROP COLUMN "
                    + DynamicModelUtils.toColumnName(column);
            mmCmpJdbc.executeUpdate(sql);
        });
    }


    protected String toColumnDef(DyModelPropDefEntity def) {
        if (def == null) {
            return "";
        }

        String type;

        switch (def.getColumnType()) {
            case INT:
                int intLen = (def.getLength() > 0 ? def.getLength() : 9);
                switch(def.getFieldBizType()) {
                    case MONEY:
                        type = "BIGINT(20)";
                        break;
                    case TM:
                    case START_TM:
                    case END_TM:
                        type = " BIGINT(20)";
                        break;
                    case INFO:
                    case STATUS:
                    case FILE:
                    case IMAGE:
                    case DICT:
                    default:
                        if(intLen<10) {
                            type = " INT(" + intLen +")";
                        }else {
                            type = " BIGINT("+intLen+")";
                        }
                        break;
                }
                break;
            case NUMBER:
                switch(def.getFieldBizType()) {
                    case MONEY:
                        type = "DECIMAL(20,2)";
                        break;
                    case TM:
                    case START_TM:
                    case END_TM:
                    case INFO:
                    case STATUS:
                    case FILE:
                    case IMAGE:
                    case DICT:
                    default:
                        type = " DECIMAL(" + (def.getLength() > 0 ? def.getLength() : 10) + ","
                                + (def.getScale() > 0 ? def.getScale() : 0) + ")";
                        break;
                }
                break;
            case TEXT:
                type = " TEXT";
                break;
            case VARCHAR:
            default:
                type = " VARCHAR(" + (def.getLength() > 0 ? def.getLength() : 10) + ")";
                break;
        }

        return DynamicModelUtils.toColumnName(def)
                + type
                + (def.getNullable() > 0 ? " NOT NULL" : "")
                + " COMMENT '" + StringEscapeUtils.escapeSql(def.getRemark()) + "'";

    }


    public Optional<DyModelPropDefEntity> findColumn(String dyColId) {
        return mmRepoProp.findById(dyColId);
    }


    public Optional<DyModelDefEntity> findModel(String dyModelId) {
        return mmRepoMdl.findById(dyModelId);
    }

    /**
     * 逐个处理字段.
     *
     * @param model    模型.
     * @param callback 字段处理callback函数.
     */
    public void foreachField(DyModelDefEntity model, Consumer<DyModelPropDefEntity> callback) {
        mmRepoProp.findByDyModelIdOrderBySeq(model.getId()).forEach(f -> {
            if (callback != null) {
                try {
                    callback.accept(f);
                } catch (Exception e) {
                    LogHelper.error(e);
                }
            }
        });
    }

    /**
     * 获得字段值.
     * <p>适配Map和Object</p>
     *
     * @param obj   对象.
     * @param field 字段信息.
     * @return 值.
     * <p>发生异常返回null,注意查看日志.</p>
     */
    public Object fetchFieldValue(Object obj, DyModelPropDefEntity field) {

        if (obj == null || field == null) {
            return null;
        }

        if (obj instanceof Map) {
            return ((Map<?, ?>) obj).get(field.getNameEn());
        } else {
            PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(obj.getClass(), field.getNameEn());
            if (pd == null) {
                return null;
            }
            Method m = pd.getReadMethod();
            if (m == null) {
                return null;
            }
            try {
                return m.invoke(obj);
            } catch (IllegalAccessException | InvocationTargetException e) {
                LogHelper.error(e);
            }
        }
        return null;
    }

    public DyModelDefEntity save(DyModelDefEntity entity) {
        return mmRepoMdl.saveAndFlush(entity);
    }

    public DyModelPropDefEntity save(DyModelPropDefEntity entity) {
        return mmRepoProp.save(entity);
    }

    public void delete(DyModelDefEntity entity) {
        mmRepoMdl.delete(entity);
    }

    public void delete(DyModelPropDefEntity entity) {
        mmRepoProp.delete(entity);
    }

}
